package com.miyake.dsp.osa;

import java.util.Iterator;
import java.util.List;

import com.miyake.dsp.ComplexArray;
import com.miyake.dsp.ScilabExecute;
import com.miyake.dsp.analyzer.PeakSearch;
import com.miyake.dsp.analyzer.PeakSearchSetting;
import com.miyake.dsp.common.UnitConverter;
import com.miyake.dsp.osa.lab.Fitting2;

public class WdmGenerator2 {
	
	private WdmProperty property;
	private OsaSetting osaSetting;
	private double[] wavelength;
	private double[] inData;
	private double[] outData;
	private int startOffset = -1;
	private int trimCount;
	
	public WdmGenerator2(WdmProperty prop) {
		this.property = prop;
		
		if (!this.property.isPreset()) {
			OsaCsv csvIn = new OsaCsv(property.getInFile());
			OsaCsv csvOut = new OsaCsv(property.getOutFile());
			this.inData = csvIn.getLevel();
			this.outData = csvOut.getLevel();
			this.wavelength = csvIn.getX();
			this.osaSetting = csvIn.getSetting();
		}
		else {
			OsaCsv csvIn = new OsaCsv(property.getInFile());
			OsaSetting setting = csvIn.getSetting();
			double[] in = csvIn.getLevel();
			
			startOffset = -1; // initializes offset
			if (property.isCustom()) {
				inData = getCustom(in, setting, property.getChannelProperty().getInChannels(), 
						property.getChannelProperty().getInChannels().get(0).wavelength,
						property.getChannelProperty().getInChannels().get(property.getChannelProperty().getInChannels().size()-1).wavelength);
			}
			else {
				inData = getNormal(in, false, setting);
			}	
			
			wavelength = new double[inData.length];
			double step =  setting.getSamplingResolution();
			double start = property.getChannelProperty().getStartWavelength() - 
					(startOffset - trimCount) * step - (setting.getSamplingPoints()-1) * step/2.0;
	
			start = Math.round(start * 100.0) / 100.0;
			for (int i = 0; i < wavelength.length; i++) {
				wavelength[i] = start + i * step;
			}
			
			OsaCsv csvOut = new OsaCsv(property.getOutFile());
			double[] out = csvOut.getLevel();
			if (property.isCustom()) {
				outData = getCustom(out, setting, property.getChannelProperty().getOutChannels(),
						property.getChannelProperty().getInChannels().get(0).wavelength,
						property.getChannelProperty().getInChannels().get(property.getChannelProperty().getInChannels().size()-1).wavelength);
				
				if (property.isWhiteNoise()) {
					outData = addWhiteNoise(outData);
				}
				
				//return ret;
				if (property.isGain()) {
					outData = addGain(outData, start, step);
				}
	
				if (property.isAse()) {
					outData = addASE(outData);
				}	
			}
			else {
				outData = getNormal(out, true, setting);
			}
				
			
			osaSetting = setting;
			osaSetting.setCenterWavelength( (wavelength[0] + wavelength[wavelength.length-1])/2.0 );
			osaSetting.setSpanWavelength(wavelength[wavelength.length-1] - wavelength[0]);
			osaSetting.setSamplingPoints(wavelength.length);
		}
	}
	
	public double[] getIn() {
		return inData;
	}
	
	public double[] getOut() {
		return outData;
	}
	
	public double[] getWavelength() {
		return wavelength;
	}
	
	private double[] getCustom(double[] source,  
			OsaSetting setting, List<ChannelElement> channels, double start, double end) {
		double step = setting.getSpanWavelength() / (source.length - 1);
		
		ChannelElement[] arr = channels.toArray(new ChannelElement[0]);
		
		int count = (int)Math.ceil((end - start) / step);
		
		int[] sampl = {501, 1001, 2001, 5001, 10001, 20001, 50001};
		
		for (int i = 0; i < sampl.length; i++) {
			if (count < sampl[i]) {
				if (startOffset < 0) {
					startOffset = (sampl[i] - count)/2;
				}
				count = sampl[i];
				break;
			}
		}
		
		double[] impulse = new double[count];
		
		try {
			//double start = arr[0].wavelength;
			for (int i = 0; i < arr.length; i++) {
				double wav = arr[i].wavelength;
				int index = (int)Math.round((wav - start) / step);
				impulse[index + startOffset] = UnitConverter.toLinear(arr[i].amplitude);
			}
		}
		catch (Exception e) {
			e.printStackTrace();
		}
		
		double[] ret = ScilabExecute.execute("y=convol(data1, data2);", new ComplexArray(source), new ComplexArray(impulse), "y").getRealPart();
		
		trimCount = (source.length-1) / 2;
		ret = trim(ret, trimCount);
		
		return ret;
	}
	
	private double[] trim(double[] in, int margin) {
		double[] ret = new double[in.length - margin*2];

		for (int i = 0; i < in.length - margin*2; i++) {
			ret[i] = in[i + margin];
		}
		return ret;
	}
	
	private double[] addGain(double[] in, double start, double step) {
		
		double[] ret = new double[in.length];
		double[] gainArray = getInterpolatedGain(start, step, in.length);
		for (int i = 0; i < ret.length; i++) {
			ret[i] = in[i] * gainArray[i];
		}
		return ret;
	}

	private double[] getGainArray(double start, double step, int count) {
		double[] ret = new double[count];
		
		double[] gain = getInterpolatedGain(start, step, count);
	
		Iterator<ChannelElement> it = property.getChannelProperty().getGains().iterator();
		ChannelElement e = it.next();
		double prevWav = e.wavelength;
		double prevAmp = e.amplitude;
		int index = 0;
		double wav = 0;
		for (int i = 0; i < count; i++) {
			wav = i*step + start;
			if (wav > e.wavelength) {
				if (it.hasNext()) {
					e = it.next();
				}
			}
			
			double g;
			if (prevWav != e.wavelength) {
				g = (prevAmp - e.amplitude) / (prevWav - e.wavelength) * (wav - prevWav) + prevAmp;
			}
			else {
				g = prevAmp;
			}
			ret[index++] = UnitConverter.toLinear(g);
		}
		
		return ret;
	}

	private double[] getInterpolatedGain(double start, double step, int count) {
		double[] interp = new double[count];
		for (int i = 0; i < count; i++) {
			interp[i] = start + step * i;
		}
		
		List<ChannelElement> g = property.getChannelProperty().getGains();
		double[] x = new double[g.size()];
		double[] y = new double[g.size()];
		int j = 0;
		for (ChannelElement e : g) {
			x[j] = e.wavelength;
			y[j] = e.amplitude;
			j++;
		}
		Fitting2 fitting = new Fitting2("y=x(1)+x(2)*t+x(3)*t^2");
		fitting.setOriginalData(x, y);
		return fitting.getInterpolated(interp);
	}
	private double getGainAt(double wavelength2) {
		
		List<ChannelElement> gains = property.getChannelProperty().getGains();
		
		Iterator<ChannelElement> it = gains.iterator();
		ChannelElement element = it.next();
		double prevWavelength = element.wavelength;
		double prevGain = element.amplitude;
		while (it.hasNext()) {
			ChannelElement e = it.next();
			if (e.wavelength > wavelength2) {
				double gain = (prevGain - e.amplitude) / (prevWavelength - e.wavelength) * (wavelength2 - prevWavelength) + prevGain;
				//System.out.println(wavelength2 + "," + gain);
				return UnitConverter.toLinear(gain);
			}
			prevWavelength = e.wavelength;
			prevGain = e.amplitude;
		}
		return 0;
	}

	private double getGain(double[] wavelength2) {
		// TODO Auto-generated method stub
		return 0;
	}

	private double[] getNormal(double[] source, boolean effect, OsaSetting setting) {
		int points = property.getPoints();
		int spacingPoint = (int)(property.getSpacing() * (double)source.length / setting.getSpanWavelength());
		int mask = 0;//(points / spacingPoint - peaks) / 2;
		double[] impulse = new double[points];
		int peak = 0;
		boolean b = false;
		
		int xoffset = 1;
		
		for (int i = xoffset; i < impulse.length; i++) {

			if (i % spacingPoint == 0) {
				peak++;
				b = true;
			}
			else {
				impulse[i] = 0;
			}
			if (peak > mask && peak < (points / spacingPoint) - mask && b) {
				impulse[i] = 1;
				b = false;
			}
		}
		double[] ret = ScilabExecute.execute("y=convol(data1, data2);", new ComplexArray(source), new ComplexArray(impulse), "y").getRealPart();

	
		double[] ret2 = new double[points];
		for (int i = 0; i < points; i++) {
			ret2[i] = ret[i + (ret.length - ret2.length)/ 2];
		}

		if (effect) {
			ret2 = getEffect(ret2);
		}
		
		wavelength = new double[ret.length];
		double step = setting.getSpanWavelength() / (source.length - 1);
		double start = setting.getCenterWavelength() - step * wavelength.length / 2;
		for (int i = 0; i < wavelength.length; i++) {
			wavelength[i] = start + step * i;
		}
		return ret2;
		//return addASE(addGain(ret));
	}



	private double[] getEffect(double[] ret2) {
		if (property.isWhiteNoise()) {
			ret2 = addWhiteNoise(ret2);
		}
		
		//return ret;
		if (property.isGain()) {
			ret2 = addGain(ret2);
		}

		if (property.isAse()) {
			ret2 = addASE(ret2);
		}
		return ret2;
	}
	
	private double[] addGain(double[] in) {
		double f = Math.PI / in.length;
		double[] ret = new double[in.length];
		for (int i = 0; i < ret.length; i++) {
			if (property.isGainFlat()) {
				ret[i] = in[i] * Math.pow(10.0, property.getGainValue()/10.0);
			}
			else {
				ret[i] = in[i] * (1 + Math.sin(i * f))/2.0 * Math.pow(10.0, property.getGainValue()/10.0);
			}
		}
		return ret;
	}

	private double[] addASE(double[] in) {
		double f = Math.PI / in.length;
		double[] ret = new double[in.length];
		for (int i = 0; i < ret.length; i++) {
			if (property.isAseFlat()) {
				ret[i] = in[i] + property.getAseValue() * 1e-6;
			}
			else {
				ret[i] = in[i] + (Math.sin(i * f) + 1) / 2.0 * property.getAseValue() * 1e-6;
			}
		}
		return ret;
	}

	private double[] addWhiteNoise(double[] in) {
		double[] ret = new double[in.length];
		for (int i = 0; i < ret.length; i++) {
			ret[i] = in[i] + Math.random() * property.getWhiteNoiseValue() * 1e-6;
		}
		return ret;	
	}
	
	public OsaSetting getOsaSetting() {
		return this.osaSetting;
	}
}
